package com.shijie.sjoj.model.component;

import com.shijie.sjoj.model.exception.SecureException;
import com.shijie.sjoj.model.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * 明文加密
 *
 * @author ZSJ
 */
@Slf4j
public class CryptoModel {

    /**
     * 密文分隔标志
     */
    private static final String Cipher_Split = "]sjoj=";

    /**
     * 密钥算法，AES
     */
    private static final String Secret_Key_Algorithm_AES = "AES";

    /**
     * 数据加密算法，AES-CBC
     */
    private static final String Algorithm_AES_CBC = "AES/CBC/PKCS5Padding";

    /**
     * 初始化向量长度，AES-CBC模式必须为16字节IV
     */
    private static final int AES_IV_LENGTH = 16;


    /**
     * 生成AES256密钥
     *
     * @return SecretKey 密钥
     */
    private static SecretKey generateAESKey() {
        try {
            // 密钥生成器实例
            KeyGenerator keyGenerator = KeyGenerator.getInstance(Secret_Key_Algorithm_AES);
            // 密钥初始化
            keyGenerator.init(256, new SecureRandom());
            // 生成并返回密钥
            return keyGenerator.generateKey();
        } catch (Exception e) {
            throw SecureException.throwError("AES-CBC-256密钥生成错误");
        }
    }

    /**
     * 明文数据加密
     *
     * @param plaintext 待加密的明文
     * @return String 加密后字符串，key + iv + data
     */
    public static String encryptData(String plaintext) {
        // 无效值校验
        if (StringUtils.isBlank(plaintext)) {
            throw SecureException.throwError("数据加密失败：代加密数据无效");
        }
        // 设置初始化向量
        byte[] iv = new byte[AES_IV_LENGTH];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        // 生成密钥
        SecretKey secretkey = generateAESKey();
        try {
            // 获取加密实例
            Cipher aes256 = Cipher.getInstance(Algorithm_AES_CBC);
            // 加密实例初始化
            aes256.init(Cipher.ENCRYPT_MODE, secretkey, ivSpec);
            // 加密数据
            byte[] encrypted = aes256.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
            // 获取密钥的base64字符串
            String key = Base64.getEncoder().encodeToString(secretkey.getEncoded());
            // 获取初始化向量的base64字符串
            String initVector = Base64.getEncoder().encodeToString(iv);
            // 获取数据的base64字符串
            String ciphertext = Base64.getEncoder().encodeToString(encrypted);
            // 返回密文
            return key + Cipher_Split + initVector + Cipher_Split + ciphertext;
        } catch (Exception e) {
            throw SecureException.throwError("数据加密失败：" + e.getMessage());
        }

    }

    /**
     * 密文数据解密
     *
     * @param cipher 待解密的密文
     * @return String 明文源数据
     */
    public static String decryptData(String cipher) {
        // 无效值校验
        if (StringUtils.isBlank(cipher)) {
            throw SecureException.throwError("数据解密失败：无效的密文");
        }
        // 分割密文
        String[] split = cipher.split(Cipher_Split);
        // 密文格式校验
        if (split.length != 3) {
            throw SecureException.throwError("数据解密失败：无效的密文");
        }
        // 获取密钥字节数组
        byte[] key = Base64.getDecoder().decode(split[0]);
        // 获取初始化向量字节数组
        byte[] iv = Base64.getDecoder().decode(split[1]);
        // 获取源数据字节数组
        byte[] ciphertext = Base64.getDecoder().decode(split[2]);
        try {
            // 密钥
            SecretKey secretKey = new SecretKeySpec(key, Secret_Key_Algorithm_AES);
            // 初始化向量
            IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
            // 获取解密实例
            Cipher aes256 = Cipher.getInstance(Algorithm_AES_CBC);
            // 解密实例初始化
            aes256.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
            // 解密数据
            byte[] bytes = aes256.doFinal(ciphertext);
            // 返回源数据
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw SecureException.throwError("数据解密失败：" + e.getMessage());
        }
    }


    /**
     * 加密算法
     */
    private static final String Algorithm_PBKDF2 = "PBKDF2WithHmacSHA256";

    /**
     * 盐值长度，16字节（128位）
     */
    private static final int Salt_Length = 16;

    /**
     * 迭代次数，2^14次
     */
    private static final int Iterator_Count = 16_384;

    /**
     * 密钥长度，256位（32字节）
     */
    private static final int Key_Length = 256;


    /**
     * 生成随机盐值16字节
     *
     * @return byte[]
     */
    private static byte[] newSalt16() {
        // 定义盐值的字节长度
        byte[] salt = new byte[Salt_Length];
        // 安全加密随机数
        SecureRandom secureRandom = new SecureRandom();
        // 随机盐值
        secureRandom.nextBytes(salt);
        // 返回生成的随机盐值
        return salt;
    }

    /**
     * 加密密码
     *
     * @param password 密码源字符
     * @return String
     */
    public static String encryptPassword(String password) {
        // 无效字符验证
        if (StringUtils.isBlank(password)) {
            throw SecureException.throwError("密码加密失败：无效的密码字符");
        }
        try {
            // 获取加密工厂
            SecretKeyFactory factory = SecretKeyFactory.getInstance(Algorithm_PBKDF2);
            // 盐值
            byte[] salt16 = newSalt16();
            // 密钥材料，基于密码的加密（Password-Based Encryption, PBE）
            PBEKeySpec pbeKeySpec = new PBEKeySpec(
                    password.toCharArray(),
                    salt16,
                    Iterator_Count,
                    Key_Length
            );
            // 生成密码加密字节数组
            byte[] pass = factory.generateSecret(pbeKeySpec).getEncoded();
            // base64获取盐值字符串
            String enSalt = Base64.getEncoder().encodeToString(salt16);
            // base64获取已加密密码字符串
            String enPassword = Base64.getEncoder().encodeToString(pass);
            // 返回加密值
            return enSalt + Cipher_Split + enPassword;
        } catch (Exception e) {
            throw SecureException.throwError("加密密码失败：" + e.getMessage());
        }
    }

    /**
     * 验证密码
     *
     * @param password 输入的密码
     * @param cipher   密码密文
     * @return boolean
     */
    public static boolean validatePassword(String password, String cipher) {
        // 密码与密文字符验证
        if (StringUtils.isBlank(password) || StringUtils.isBlank(cipher)) {
            log.info(LogAnsi.red("密码验证失败：无效的输入密码或密码密文"));
            return false;
        }
        // 密文格式验证
        String[] split = cipher.split(Cipher_Split);
        if (split.length != 2) {
            log.info(LogAnsi.red("密码验证失败：无效的密文"));
            return false;
        }
        // 获取盐值
        byte[] salt = Base64.getDecoder().decode(split[0]);
        // 获取密文密码
        byte[] pass = Base64.getDecoder().decode(split[1]);
        // 密钥材料
        PBEKeySpec pbeKeySpec = new PBEKeySpec(
                password.toCharArray(),
                salt,
                Iterator_Count,
                Key_Length
        );
        try {
            // 获取加密工厂
            SecretKeyFactory factory = SecretKeyFactory.getInstance(Algorithm_PBKDF2);
            // 加密输入的密码
            byte[] encoded = factory.generateSecret(pbeKeySpec).getEncoded();
            // 比较并返回加密的输入密码与密文密码值是否相等
            return MessageDigest.isEqual(encoded, pass);
        } catch (Exception e) {
            log.info(LogAnsi.red("密码验证失败：{}"), e.getMessage());
            return false;
        }
    }


}
